package com.smallstep.hystrix.utils;

import com.smallstep.hystrix.annotation.FFHystrixBean;
import com.smallstep.hystrix.annotation.FFHystrixFallback;
import com.smallstep.hystrix.entity.FFFallbackMethod;
import com.smallstep.hystrix.entity.FFPathPattern;
import com.smallstep.hystrix.entity.FFRequestKey;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.util.*;

/**
 * @author 李俊
 */
public class FFHystrixFallbackUtil implements ApplicationContextAware {

    private static List<FFFallbackMethod> FALLBACK_METHODS = new ArrayList();

    public FFHystrixFallbackUtil() {

    }

    /**
     *
     * @param requestKey
     * @return
     */
    public static FFFallbackMethod getFallbackMethod(final FFRequestKey requestKey) {
        for (int i = 0; i < FALLBACK_METHODS.size(); i++) {
            FFFallbackMethod fallbackMethod = FALLBACK_METHODS.get(i);
            if (FFHystrixFallbackUtil.isMatch(fallbackMethod,requestKey)) {
                return fallbackMethod;
            }
        }
        return  null;
    }

    /**
     * 判断是否匹配
     * @param fallbackMethod
     * @param requestKey
     * @return
     */
    public static boolean isMatch(FFFallbackMethod fallbackMethod, FFRequestKey requestKey) {
        boolean isServiceMatch = fallbackMethod.getService() == null || fallbackMethod.getService().equals(requestKey.getService());
        if (!isServiceMatch) {
            return false;
        } else {
            boolean isPathMatch = false;
            // 判断路径是否匹配
            if (CollectionUtils.isEmpty(fallbackMethod.getPathPatterns())) {
                isPathMatch = true;
            } else {
                AntPathMatcher antPathMatcher = new AntPathMatcher();
                List<FFPathPattern> ffPathPatternList = fallbackMethod.getPathPatterns();
                for (int i = 0; i < ffPathPatternList.size(); i++) {
                    FFPathPattern pathPattern = ffPathPatternList.get(i);
                    if (pathPattern.getMethod() != null &&
                            antPathMatcher.match(pathPattern.getPattern(),requestKey.getPath())) {
                        isPathMatch = true;
                        break;
                    }
                }
            }
            if (!isPathMatch) {
                return false;
            } else {
                // 判断返回类型是否一致
                boolean isReturnTypeMatch = false;
                if (!fallbackMethod.isResponseTypeSensitive()) {
                    isReturnTypeMatch = true;
                } else {
                    Type returnType = fallbackMethod.getMethod().getGenericReturnType();
                    if (returnType.equals(ResponseEntity.class)) {
                        returnType = Void.class;
                    } else if (returnType instanceof ParameterizedType && ((ParameterizedType)returnType).getRawType().equals(ResponseEntity.class)) {
                        returnType = ((ParameterizedType)returnType).getActualTypeArguments()[0];
                    }

                    if (returnType.equals(requestKey.getResponseType())) {
                        isReturnTypeMatch = true;
                    }
                }
                return isReturnTypeMatch;
            }
        }
    }

    /**
     *
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Environment environment = applicationContext.getEnvironment();
        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(FFHystrixBean.class);
        List<FFFallbackMethod> fallbackMethods = new ArrayList();
        for (Map.Entry<String,Object> entry : beans.entrySet()){
            Object bean = entry.getValue();
            Method[] methods = bean.getClass().getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                FFHystrixFallback fallbackAnno = method.getAnnotation(FFHystrixFallback.class);
                if(fallbackAnno != null){
                    FFFallbackMethod fallbackMethod = new FFFallbackMethod();
                    if (StringUtils.isNotEmpty(fallbackAnno.service())) {
                        String service = environment.resolvePlaceholders(fallbackAnno.service());
                        try {
                            fallbackMethod.setService(UrlUtil.getHost(UrlUtil.format(service)));
                        } catch (MalformedURLException var18) {

                        }
                    }
                    List<FFPathPattern> pathPatterns = new ArrayList();
                    String[] paths = fallbackAnno.pathPatterns();
                    for (int j = 0; j < paths.length; j++) {
                        pathPatterns.addAll(UrlUtil.splitPathPatterns(paths[j]));
                    }
                    fallbackMethod.setPathPatterns(pathPatterns);
                    fallbackMethod.setResponseTypeSensitive(fallbackAnno.responseTypeSensitive());
                    fallbackMethod.setPriority(fallbackAnno.priority());
                    fallbackMethod.setBean(bean);
                    fallbackMethod.setMethod(method);
                    fallbackMethod.setCircuitBreakerEnabled(fallbackAnno.circuitBreakerEnabled());
                    fallbackMethod.setCircuitBreakerErrorThresholdPercentage(fallbackAnno.circuitBreakerErrorThresholdPercentage());
                    fallbackMethod.setCircuitBreakerRequestVolumeThreshold(fallbackAnno.circuitBreakerRequestVolumeThreshold());
                    fallbackMethod.setCircuitBreakerSleepWindowInMilliseconds(fallbackAnno.circuitBreakerSleepWindowInMilliseconds());
                    fallbackMethods.add(fallbackMethod);
                }
            }
        }
        Collections.sort(fallbackMethods, (o1, o2) -> o2.getPriority() - o1.getPriority());

        FALLBACK_METHODS = fallbackMethods;
    }
}
